#include "udpServer.hpp"
#include "onlineUser.hpp"
#include <iostream>
#include <memory>
#include <unordered_map>
#include <fstream>
#include <signal.h>

using namespace server;
using namespace userManager;

onlineUser oluser;

const static std::string dictPath = "./dict.txt";
std::unordered_map<std::string, std::string> dict; // 字典

static void Usage(const std::string &proc)
{
    std::cout << "\nUsage:\n\t" << proc << " local_port" << std::endl
              << std::endl;
}

static bool cutString(const std::string &targetLine, std::string *str1, std::string *str2, const std::string &sep)
{
    auto pos = targetLine.find(sep);
    if (pos == std::string::npos)
        return false;

    *str1 = targetLine.substr(0, pos);
    *str2 = targetLine.substr(pos + sep.size());

    return true;
}

static void initDict()
{
    // 1. 打开配置文件
    std::ifstream in(dictPath, std::ios::binary);
    if (!in.is_open())
    {
        std::cerr << "open file error: " << errno << " : " << strerror(errno) << std::endl;
        exit(OPEN_ERROR);
    }

    // 2. 读取配置文件
    std::string line;
    std::string key, value;
    /* istream& getline (istream& is, string& str);getline内部已经对返回值类型做了重载，所以while判断中返回的实际是bool类型 */
    while (getline(in, line))
    {
        // std::cout << line << std::endl;// 测试用

        if (cutString(line, &key, &value, ":")) // 以":"为分隔符截取字符串，key放key中，value放value中
        {
            // 截取成功
            dict.insert(std::make_pair(key, value));
        }
        else
        {
            std::cerr << "read txt error: " << errno << " : " << strerror(errno) << std::endl;
            exit(TXT_ERROR);
        }
    }

    in.close();

    std::cout << "load dict success." << std::endl; // 日志
}

void reloadDict(int signo)
{
    (void)signo; // 不关心是哪个信号，只有来信号，就触发
    initDict();
}

static void printDict()
{
    for (auto &s : dict)
    {
        std::cout << s.first << "->" << s.second << std::endl;
    }
}

// 业务逻辑一-翻译单词
void handleMessage(int sockfd, std::string &clientIP, uint16_t clientPort, std::string &msg)
{
    // 在这里对msg进行业务处理，不关心msg怎么来的 —— server通信和业务逻辑的解耦

    // 业务逻辑

    // 客户端不仅能向服务器发送数据，还要能接收服务器的消息
    std::string response_msg; // 响应数据 -- 对客户端的消息做出响应

    auto it = dict.find(msg);
    if (it == dict.end())
        response_msg = "unknown messags";
    else
        response_msg = it->second;

    // 发送响应消息给客户端
    struct sockaddr_in client;
    bzero(&client, sizeof(client));

    client.sin_family = AF_INET;
    client.sin_port = htons(clientPort);
    client.sin_addr.s_addr = inet_addr(clientIP.c_str());

    sendto(sockfd, response_msg.c_str(), response_msg.size(), 0, (struct sockaddr *)&client, sizeof(client));
}

// 业务逻辑二-执行命令
void executeCommand(int sockfd, std::string &clientIP, uint16_t clientPort, std::string &cmd)
{
    // 1. 解析命令

    // 2. 处理
    // a. fork + exec
    // b. popen = pipe + fork + exec*

    // b.
    if (cmd.find("rm") != std::string::npos || cmd.find("rmdir") != std::string::npos || cmd.find("mv") != std::string::npos)
    {
        // std::cerr << "rm && rmdir && mv is forbid use" << std::endl;
        std::cerr << clientIP << "[" << clientPort << "] 正在执行一个非法操作：" << cmd << std::endl;
        return;
    }

    std::string response_message;
    FILE *fp = popen(cmd.c_str(), "r");
    if (fp == nullptr)
        response_message = cmd + " execute fail";

    char lineBuffer[1024] = {0};
    while (fgets(lineBuffer, sizeof(lineBuffer), fp))
    {
        response_message += lineBuffer;
    }

    pclose(fp);

    // 发送响应消息给客户端
    struct sockaddr_in client;
    bzero(&client, sizeof(client));

    client.sin_family = AF_INET;
    client.sin_port = htons(clientPort);
    client.sin_addr.s_addr = inet_addr(clientIP.c_str());

    sendto(sockfd, response_message.c_str(), response_message.size(), 0, (struct sockaddr *)&client, sizeof(client));
}

// 业务逻辑三-数据路由
void routeMessage(int sockfd, std::string &clientIP, uint16_t clientPort, std::string &msg)
{
    if (msg == "online")
        oluser.addUser(clientIP, clientPort);
    if(msg == "offline")
        oluser.delUser(clientIP, clientPort);

    // 向在线用户发送消息——先上线，再发消息
    if (oluser.isOnline(clientIP, clientPort))
    {
        // 消息的路由
        oluser.broadcastMsg(sockfd, clientIP, clientPort, msg);
    }
    else
    {
        // 单发
        std::string response_message("你还没有上线，请先上线！执行命令：online");
        oluser.singleMsg(sockfd, clientIP, clientPort, response_message);
    }

    /*
    1. 服务器端开启服务器并将客户端发来的消息重定向到管道fifo中
    2. 客户端向服务端发消息
    3. 一个进程执行"cat < fifo"命令充当公共聊天室的公共面板
    */
}

/*udpServer的运行
格式：./udpServer port
*/
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERROR);
    }

    // 加载词库
    initDict();
    // 打印词库-测试
    // printDict();

    // 热加载词典
    // signal(2, reloadDict);

    // 1. 创建服务器
    uint16_t port = atoi(argv[1]);
    // std::string ip = argv[1];
    /* ip:
    a. 127.0.0.1： 本地环回。一般用于服务器代码的测试。（应用层->传输层->网络层->数据链路层->网络层->传输层->应用层）同一主机
    b. 云服务的公网ip：云服务器是虚拟化的服务器，不能直接绑定云服务的公网ip。只是我们可以通过这个公网IP来找到我们的那台服务器。
    c. 虚拟机或者Linux机器：可以绑定自己主机的IP。
    */

    // std::unique_ptr<udpServer> usvr(new udpServer(handleMessage, port));// 执行业务逻辑一
    // std::unique_ptr<udpServer> usvr(new udpServer(executeCommand, port)); // 执行业务逻辑二
    std::unique_ptr<udpServer> usvr(new udpServer(routeMessage, port)); // 执行业务逻辑三

    // 2. 初始化服务器
    usvr->initServer();
    // 3. 启动服务器
    usvr->start();

    /*
    netstat -nuap：查看udp相关
        -u:udp
        -a:all
        -n:能以数字形式显式的就显示成数字
        -p:proc-进程
    */

    return 0;
}